# coding=utf-8
import os
import numpy as np
import pandas as pd
import collections


# from scipy import interpolate


class Point(object):
    x = 0
    y = 0

    # 定义构造方法
    def __init__(self, x=0, y=0):
        self.x = x
        self.y = y


class Line(object):
    # a=0
    # b=0
    # c=0
    def __init__(self, p1, p2):
        self.p1 = p1
        self.p2 = p2


def GetLinePara(line):
    line.a = line.p1.y - line.p2.y;
    line.b = line.p2.x - line.p1.x;
    line.c = line.p1.x * line.p2.y - line.p2.x * line.p1.y;


def GetCrossPoint(l1, l2):
    GetLinePara(l1);
    GetLinePara(l2);
    d = l1.a * l2.b - l2.a * l1.b
    p = Point()
    p.x = (l1.b * l2.c - l2.b * l1.c) * 1.0 / d
    p.y = (l1.c * l2.a - l2.c * l1.a) * 1.0 / d
    return p


def get_cross_point(p1, p2, p3, p4):
    ## 计算由点p1,p2组成的直线和点p3,p4组成的直线的交点。
    p1 = Point(p1[0], p1[1])
    p2 = Point(p2[0], p2[1])
    p3 = Point(p3[0], p3[1])
    p4 = Point(p4[0], p4[1])

    line1 = Line(p1, p2)
    line2 = Line(p3, p4)
    pc = GetCrossPoint(line1, line2)
    return (pc.x, pc.y)


def distance(p0, p1):
    """
    计算二维点间的距离
    """
    drawer = []
    drawer.append({'line': [(p0[0], p0[1]), (p1[0], p1[1])]})
    value = np.linalg.norm(np.asarray(p1) - np.asarray(p0))
    value = float(value) * 0.139
    return value, drawer


def distance_point_to_line(p, ls, le):
    """
    计算点p到直线ls_le的距离,二维
    """
    return distance(p, cross_point(p, ls, le))


def cross_point(p, ls, le):
    try:
        k = (le[1] - ls[1]) / (le[0] - ls[0])
        A = np.asarray([[k, -1], [1.0 / k, 1]])
        b = np.asarray([k * ls[0] - ls[1], p[0] / k + p[1]])
        return np.linalg.solve(A, b)
    except ZeroDivisionError:
        return np.asanyarray([ls[0], p[1]])


def point_location(p, ls, le, seg=4):
    """
    点在线段上的位置，用于计算滑脱分度。
    :param p: 判断点
    :param ls: 线段起点
    :param le: 线段终点
    :param seg: 线段数
    :return:
    """
    value = seg * (cross_point(p, ls, le)[0] - ls[0]) / (le[0] - ls[0])
    value = float(value)
    return value


def degree_four_points(v0_s, v0_e, v1_s, v1_e):
    """
    计算由四个点组成的两个向量间的夹角，用于计算侧弯角等。
    三个点的也可构造成四个点调用该函数。
    :param v0_s:第一个向量的起始点
    :param v0_e:第一个向量的终止点
    :param v1_s:第二个向量的起始点
    :param v1_e:第二个向量的终止点
    """
    v0 = np.asarray(v0_e) - np.asarray(v0_s)
    v1 = np.asarray(v1_e) - np.asarray(v1_s)
    ## 计算两条直线的交点，用来画角度标识的线。
    crosspoint = get_cross_point(v0_s, v0_e, v1_s, v1_e)
    drawer = [{'angle': [[crosspoint, v0_e], [crosspoint, v1_e]]}]
    value = np.rad2deg(angle_between_vectors(v0, v1))
    value = float(value)
    return value, drawer


def min_angle_between_vector_and_horizontal_line(vs, ve):
    """
    计算向量与水平向量的最小夹角
    """
    v = np.asarray(ve) - np.asarray(vs)
    vh = np.asarray([ve[0], vs[1]]) - np.asarray(vs)
    drawer = [{'angle': [[vs, ve], [vs, [ve[0], vs[1]]]]}]
    value = np.rad2deg(angle_between_vectors(v, vh))
    value = float(value)
    return value, drawer


def min_angle_between_vector_and_vertical_line(vs, ve):
    """
        计算向量与竖直向量的最小夹角
        """
    v = np.asarray(ve) - np.asarray(vs)
    vt = np.asarray([vs[0], ve[1]]) - np.asarray(vs)
    drawer = [{'angle': [[vs, ve], [vs, [vs[0], ve[1]]]]}]
    value = np.rad2deg(angle_between_vectors(v, vt))
    value = float(value)
    return value, drawer


def vertical_and_straight_line(points):
    """
    判断输入点集是否是垂直线。根据输入点集x轴的方差与允许误差比较。
    :param points:
    :param deta:
    :return:
    """
    x = [p[0] for p in points]
    y = [p[1] for p in points]
    std = np.std(x) * 0.139
    avx = np.mean(x)
    sp = [avx, min(y)]
    ep = [avx, max(y)]
    drawer = []
    drawer.append({'line': [tuple(sp), tuple(ep)]})
    drawer.append({'point': points})
    return std, drawer


def equal_distance(a, b, c, ):
    """
    判断三角形abc是ca与cb是否相等。
    :param a:
    :param b:
    :param c:
    :return:
    """
    nc = np.asarray(c)
    ca = np.linalg.norm(nc - np.asarray(a))
    cb = np.linalg.norm(nc - np.asarray(b))
    drawer = []
    drawer.append({'line': [c, a]})
    drawer.append({'line': [c, b]})
    return (abs(ca - cb) * 0.139), drawer


def curve_fit(points, power=3, seg=30):
    """
    根据一堆点采用“最小二乘法”拟合曲线，返回拟合后的点集与最大距离
    :param points:
    :param power:曲线阶数
    :param seg:返回点的个数
    :return:
    """
    power = power if power < len(points) else len(points) - 1
    x0, y0 = points[-1][:2]
    x1, y1 = points[0][:2]

    dist = np.linalg.norm(np.asarray(points[-1][:2]) - np.asarray(points[0][:2]))

    cos_theta = (x1 - x0) / dist
    sin_theta = (y1 - y0) / dist

    w2lc_matrix = np.asarray([[cos_theta, sin_theta],
                              [-sin_theta, cos_theta]])
    lc2w_matrix = np.asarray([[cos_theta, - sin_theta],
                              [sin_theta, cos_theta]])
    w2lc = lambda p: w2lc_matrix.dot(np.asarray(p[:2]) - np.asarray([x0, y0]))
    lc2w = lambda p: lc2w_matrix.dot(np.asarray(p[:2])) + np.asarray([x0, y0])

    x = []
    y = []
    for p in points:
        lcp = w2lc(p)
        x.append(lcp[0])
        y.append(lcp[1])

    # curve = interpolate.lagrange(x, y)
    curve = np.poly1d(np.polyfit(x, y, power))
    # --求最大值
    dx = (x[-1] - x[0]) / seg
    pts = []
    mv = 0
    for idx in range(seg + 1):
        xs = x[0] + idx * dx
        ys = curve(xs)
        if abs(ys) >= mv:
            mv = abs(ys)
        wp = lc2w([xs, ys])
        wp = np.asarray([wp[0], wp[1]])
        pts.append(wp)
    return pts, mv


def curve_deflection(points, power=3, seg=30):
    """
    获取曲线到曲线端点连线的最大距离
    :return:
    """
    _, v = curve_fit(points, power=power, seg=seg)
    v = float(v) * 0.139
    return v


def min_distance_between_points_pairs(points):
    """
    计算一组点副中距离最短值
    """
    pairs = [(points[2 * i], points[2 * i + 1]) for i in range(int(len(points) / 2.0))]
    distance_l = [np.linalg.norm(pair[0] - pair[1]) for pair in pairs]
    return min(distance_l)


def mean_distance(points):
    """
    计算一组点副中距离平均值
    """
    pairs = [(points[2 * i], points[2 * i + 1]) for i in range(int(len(points) / 2.0))]
    distance_l = [np.linalg.norm(pair[0] - pair[1]) for pair in pairs]
    return np.mean(distance_l)


def height_ratio(p1, p2, p3, p4):
    """
    :return: 用于椎体高度比例系数。注意顺序。
    """
    drawer = [{'line': [p1, p2]}, {'line': [p3, p4]}, {'line': [p1, p3]}, {'line': [p2, p4]}, ]
    p1 = np.asarray(p1)
    p2 = np.asarray(p2)
    p3 = np.asarray(p3)
    p4 = np.asarray(p4)

    l12 = np.linalg.norm(p1 - p2)
    l34 = np.linalg.norm(p3 - p4)
    l13 = np.linalg.norm(p1 - p3)
    l24 = np.linalg.norm(p2 - p4)
    value = (l12 + l34) / (l13 + l24)
    value = float(value)
    return value, drawer


def ratio_two_lines(p1, p2, p3, p4):
    """
    :return: 计算两个线段的比值
    """
    p1 = np.asarray(p1)
    p2 = np.asarray(p2)
    p3 = np.asarray(p3)
    p4 = np.asarray(p4)
    l12 = np.linalg.norm(p1 - p2)
    l34 = np.linalg.norm(p3 - p4)
    return l12 / l34


def vector_norm(data, axis=None, out=None):
    data = np.array(data, dtype=np.float64, copy=True)
    if out is None:
        if data.ndim == 1:
            return np.sqrt(np.dot(data, data))
        data *= data
        out = np.atleast_1d(np.sum(data, axis=axis))
        np.sqrt(out, out)
        return out
    else:
        data *= data
        np.sum(data, axis=axis, out=out)
        np.sqrt(out, out)


def angle_between_vectors(v0, v1, directed=True, axis=0):
    v0 = np.array(v0, dtype=np.float64, copy=False)
    v1 = np.array(v1, dtype=np.float64, copy=False)
    dot = np.sum(v0 * v1, axis=axis)
    dot /= vector_norm(v0, axis=axis) * vector_norm(v1, axis=axis)
    return np.arccos(dot if directed else np.fabs(dot))


AXIAL_VERTEBRAL = ["T12", "L1", "L2", "L3", "L4", "L5"]
AXIAL_LOCATION = ["HR", "SP", "ML", "HL", "FR", "FL"]

SAG_VERTEBRAL = ["T12", "L1", "L2", "L3", "L4", "L5", "S1"]
SAG_LOCATION = ["HP", "FP", "HM", "FM", "HA", "FA", "SP"]


class Rois(object):
    """
    用于计算多个roi间的相关指标，显示指标，交互更新等。记录与验证符合要求的roi
    """
    index = 0

    def __init__(self):
        Rois.index += 1
        # self.node = node
        self.index = Rois.index
        self.drawer = []
        self.fmt = None

    def angle_between_vectors(self, v0, v1, directed=True, axis=0):
        v0 = np.array(v0, dtype=np.float64, copy=False)
        v1 = np.array(v1, dtype=np.float64, copy=False)
        dot = np.sum(v0 * v1, axis=axis)
        dot /= vector_norm(v0, axis=axis) * vector_norm(v1, axis=axis)
        return np.arccos(dot if directed else np.fabs(dot))

    # def match(self, roi):
    #     """
    #     判断roi是否属于算子：
    #         1.如果roi没有选择属性或者，roi的属性不属于该算子需要的，则返回False
    #         2.如果算子已有符合的roi，新加入的roi.affine_matrix与已有的affine_matrix不一样，返回False
    #     :param roi:
    #     :return:
    #     """
    #     if not hasattr(roi, "property"):
    #         return False
    #     if not isinstance(roi.property, dict):
    #         return False
    #     opts = roi.property[self.node_id]
    #     if not opts:
    #         return False
    #     for arg in self.args:
    #         if arg in opts:
    #             if hasattr(self, "_affine_matrix") and hasattr(self, "affine_center"):
    #                 if not np.allclose(self.affine_center, roi.affine_center):
    #                     return False
    #                 if not np.allclose(self.affine_matrix, roi.affine_matrix):
    #                     return False
    #             if self.args[arg] is not None:
    #                 return self.args[arg]
    #             else:
    #                 if not hasattr(self, "_affine_matrix") or not hasattr(self, "affine_center"):
    #                     self.__setattr__("affine_center", roi.affine_center)
    #                     self.affine_matrix = roi.affine_matrix
    #                 return arg
    #     return False
    #
    # def remove_mismatch(self, roi):
    #     """
    #     判断roi是否不属于算子
    #     :param roi:
    #     :return:
    #     """
    #     for arg in self.args:
    #         if self.args[arg] == roi:
    #             if not hasattr(roi, "property"):
    #                 self.args[arg] = None
    #                 return True
    #             if not isinstance(roi, dict):
    #                 self.args[arg] = None
    #                 return True
    #             if arg not in roi.property[self.node_id]:
    #                 self.args[arg] = None
    #                 return True
    #             if hasattr(self, "affine_matrix"):
    #                 if not np.allclose(self.affine_matrix, roi.affine_matrix):
    #                     self.args[arg] = None
    #                     return True
    #             else:
    #                 self.args[arg] = None
    #                 return True
    #
    # def remove(self, roi):
    #     num = 0
    #     for arg in self.args:
    #         if self.args[arg] == roi:
    #             self.args[arg] = None
    #             self.drawer.clear()
    #             self.update(redraw=False)
    #         if self.args[arg] is not None:
    #             num += 1
    #     if num == 0:
    #         if hasattr(self, "_affine_matrix"):
    #             del self._affine_matrix
    #         if hasattr(self, "affine_center"):
    #             del self.affine_center

    # @property
    # def operator(self):
    #     return self.node.get("operator", {})
    #
    # @property
    # def name(self):
    #     return self.node.get("name")

    def fmt_value(self, value, fmt="%.2f"):
        fmt = fmt if self.fmt is None else self.fmt
        try:
            return fmt % value
        except Exception as e:
            return str(value)

    #
    # @property
    # def args(self):
    #     if not hasattr(self, "_args"):
    #         self.__setattr__("_args", {arg: None for arg in self.operator.get("args", [])})
    #     return self.__getattribute__("_args")

    # @property
    # def fmt(self):
    #     return self.operator.get("kwargs", {}).get("fmt", None)
    #
    # @property
    # def node_id(self):
    #     return self.operator.get("id")
    #
    # # @property
    # # def value_node(self):
    # #     return self.node.get("id")
    #
    # @property
    # def func(self):
    #     return self.operator.get("func", None)
    #
    # @property
    # def kwargs(self):
    #     return self.operator.get("kwargs", {})

    # @property
    # def update_ot_panel(self):
    #     return self.kwargs.get("update_other_panels", False)

    # @property
    # def available_when(self):
    #     return self.node.get("available_when")
    #
    # def draw_to_canvas(self, gc, canvas):
    #     draws = self.drawer.draws
    #     if not draws or not isinstance(draws, list):
    #         return
    #     roi = self.operator_rois[0]
    #     for draw in draws:
    #         points = draw.get('points', [])
    #         if len(points) == 0:
    #             continue
    #         func = draw.get("func")
    #         if func not in dir(canvas):
    #             continue
    #         points = self.world2view(canvas.evt_renderer, points=points)
    #         kwargs = draw.get("kwargs", {})
    #         self.OnDraw(gc, canvas, points, func, **kwargs)
    #
    # def OnDraw(self, gc, canvas, points, func, **kwargs):
    #     if func == self.drawer.CIRCLE:
    #         c = points[0]
    #         r = int(round((np.linalg.norm(np.asarray(points[0]) - np.asarray(points[1])))))
    #         args = [c, r]
    #     elif func in [self.drawer.TEXT, self.drawer.TEXT_BOX]:
    #         txt = kwargs.pop("text", "")
    #         args = [txt, points[0]]
    #     else:
    #         args = points
    #
    #     func = "canvas." + func + "(*args, **kwargs)"
    #     try:
    #         eval(func)
    #     except Exception as e:
    #         print(e)

    def distance(self, *points, **kwargs):
        """
        :return: 两点间的距离（二范数），旋转坐标系
        """
        self.drawer.clear()
        self.drawer.append({'line', points[:2]})
        return np.linalg.norm(points[1] - points[0]), self.drawer

    # def distance_point_to_line(self, *points, **kwargs):
    #     """
    #     计算点同一平面上pc到直线ps,pe的距离；
    #     取点顺序是：pc, ps, pe
    #     """
    #
    #     pl = self.project_point_3d(points)
    #     value = np.linalg.norm(pl - points[0])
    #     self.drawer.clear()
    #     self.drawer.append(self.drawer.LINE, [points[0], pl])
    #     self.drawer.append(self.drawer.TEXT, [pl], text=self.fmt_value(value))
    #     return value
    #
    # def show_line(self, *points, value="", **kwargs):
    #     self.drawer.clear()
    #     self.drawer.append(self.drawer.LINE, points[:2])
    #     return value
    #
    # def point_location(self, *points, seg=4, **kwargs):
    #     """
    #     计算垂足点的分度区域,取点顺序为ps, pe, pc
    #     """
    #     ps, pe, pc = points[:3]
    #     pl = self.project_point_3d([pc, ps, pe])
    #     v_sl = pl - ps
    #     v_se = pe - ps
    #     value = seg * v_sl.dot(v_se) / v_se.dot(v_se)
    #     self.drawer.clear()
    #     self.drawer.append(self.drawer.LINE, [ps, pe])
    #     self.drawer.append(self.drawer.LINE, [pc, pl])
    #     self.drawer.append(self.drawer.TEXT, [pl], text=self.fmt_value(value))
    #     for s in range(seg - 1):
    #         pt = ps + (s+1)*v_se/seg
    #         self.drawer.append(self.drawer.CIRCLE, [pt, pt + 0.2], width=4)
    #     return value

    @staticmethod
    def axis_number(points, err=0.1):
        axis = []
        for num in range(len(points[0])):
            if abs(np.std([p[num] for p in points])) < err:
                axis.append(num)
        if len(axis) == 1:
            return axis[0]
        elif len(axis) > 1:
            return axis
        else:
            return False

    @staticmethod
    def xy_point(point, axis):
        return [point[i] for i in range(len(point)) if i != axis]

    @staticmethod
    def project_point_3d(points):
        """
        求点o在直线ab上的垂足点
        """
        po, pa, pb = points[:3]
        v_ab = pb - pa
        v_ao = pa - po
        k = - v_ab.dot(v_ao) / v_ab.dot(v_ab)
        return k * v_ab + pa

    # def angle_four_points(self, *points, **kwargs):
    #     """
    #     可参考blog.csdn.net/xdedzl/article/details/86009147优化
    #     :param args: points, 长度至少为4的列表，point为ndarray
    #     :param kwargs:
    #     :return: 返回向量p1p0与p2p3间的距离
    #     """
    #
    #     v1 = points[0] - points[1]
    #     v2 = points[3] - points[2]
    #     value = np.rad2deg(self.angle_between_vectors(v1, v2))
    #
    #     # 设置显示图形
    #     axis = self.axis_number(points)
    #     pts = [self.xy_point(p, axis) for p in points]
    #     x = []
    #     y = []
    #     x1 = []
    #     y1 = []
    #     for p in pts[:2]:
    #         x.append(p[0])
    #         y.append(p[1])
    #     for p in pts[2:4]:
    #         x1.append(p[0])
    #         y1.append(p[1])
    #     c = interpolate.lagrange(x, y)
    #     c1 = interpolate.lagrange(x1, y1)
    #     root = (c1 - c).roots[0]
    #     center = [root, c(root)]
    #     center.insert(axis, points[0][axis])
    #     self.drawer.clear()
    #     self.drawer.append(self.drawer.ARC, [center, points[1], points[2]])
    #     self.drawer.append(self.drawer.LINE, [center, points[0]], arrow_end=True)
    #     self.drawer.append(self.drawer.LINE, [center, points[3]], arrow_end=True)
    #     self.drawer.append(self.drawer.TEXT, [center], text=self.fmt_value(value))
    #     return value
    #
    # def min_angle_three_points(self, *points, **kwargs):
    #     a, b, c = points
    #     theta = np.rad2deg(self.angle_between_vectors(b - a, c - b))
    #     value = min(theta, 180 - theta)
    #
    #     self.drawer.clear()
    #     self.drawer.append(self.drawer.LINE, [b, a], arrow_end=True)
    #     self.drawer.append(self.drawer.LINE, [b, c], arrow_end=True)
    #
    #     fa = 2*b - a
    #     if np.linalg.norm(c - a) <= np.linalg.norm(c - fa):
    #         self.drawer.append(self.drawer.ARC, [b, 0.7*b + 0.3*a, 0.7*b + 0.3*c])
    #         self.drawer.append(self.drawer.TEXT, [0.7*b + 0.15*c + 0.15*a], text=self.fmt_value(value))
    #     else:
    #         self.drawer.append(self.drawer.ARC, [b, 0.7*b + 0.3*fa, 0.7*b + 0.3*c])
    #         self.drawer.append(self.drawer.TEXT, [0.7*b + 0.15*c + 0.15*fa], text=self.fmt_value(value))
    #         self.drawer.append(self.drawer.LINE, [b, fa])
    #     return value
    #
    # def min_angle_between_vector_and_horizontal_line(self, *points, **kwargs):
    #     """
    #     计算两点组成的向量与水平线的夹角,需优化
    #     """
    #     axis = self.axis_number(points, 10**-4)
    #     if axis is False:
    #         return "病灶不在同一平面上"
    #     if isinstance(axis, list):
    #         return "病灶在同一水平或竖直线上"
    #
    #     pts = [self.xy_point(p, axis) for p in points]
    #     a, b = pts[:2]
    #     a = np.asarray(a)
    #     b = np.asarray(b)
    #     c = [b[0], a[1]]
    #     value = np.rad2deg(self.angle_between_vectors(b - a, np.asarray(c) - a))
    #     a, b = points[:2]
    #     self.drawer.clear()
    #     self.drawer.append(self.drawer.LINE, [a, b], arrow_end=True)
    #     c.insert(axis, a[axis])
    #     c = np.asarray(c)
    #     self.drawer.append(self.drawer.LINE, [a, c])
    #     self.drawer.append(self.drawer.ARC, [a, 0.7*a + 0.3*b, 0.7*a + 0.3*c])
    #     self.drawer.append(self.drawer.TEXT, [0.7*a + 0.15*b + 0.15*c], text=self.fmt_value(value))
    #     return value

    # def min_angle_between_vector_and_vertical_line(self, *points, **kwargs):
    #     """
    #     计算两点组成的向量与竖直线的夹角，同上，需优化
    #     """
    #     axis = self.axis_number(points, 10**-4)
    #     if axis is False:
    #         return "病灶不在同一平面上"
    #     if isinstance(axis, list):
    #         return "病灶在同一水平或竖直线上"
    #     pts = [self.xy_point(p, axis) for p in points]
    #     a, b = pts[:2]
    #     a = np.asarray(a)
    #     b = np.asarray(b)
    #     c = [a[0], b[1]]
    #     value = np.rad2deg(self.angle_between_vectors(b - a, np.asarray(c) - a))
    #     a, b = points[:2]
    #     c.insert(axis, a[axis])
    #     c = np.asarray(c)
    #     self.drawer.clear()
    #     self.drawer.append(self.drawer.LINE, [a, b], arrow_end=True)
    #     self.drawer.append(self.drawer.LINE, [a, c], arrow_end=True)
    #     self.drawer.append(self.drawer.ARC, [a, 0.7 * a + 0.3 * b, 0.7 * a + 0.3 * c])
    #     self.drawer.append(self.drawer.TEXT, [0.7 * a + 0.15 * b + 0.15 * c], text=self.fmt_value(value))
    #     return value

    def vertical_and_straight_line(self, *points, deta=1.0, **kwargs):
        """
        判断输入点是否是垂直直线
        :param args:
        :param kwargs:
            points: 点列表，point 为ndarray
            deta: 误差值大小
        :return:
        """
        axis = self.axis_number(points, 10 ** -4)
        # if axis is False:
        #     return "病灶未在同一平面。"
        # if isinstance(axis, list):
        #     std = 0
        #     self.drawer.clear()
        #     self.drawer.append({'line': [points[0], points[-1]]})
        # else:
        pts = points
        x = [p[0] for p in pts]
        std = np.std(x)
        avx = np.mean(x)
        sp = [avx, pts[0][-1]]
        ep = [avx, pts[-1][-1]]
        # sp.insert(axis, points[0][axis])
        # ep.insert(axis, points[0][axis])
        self.drawer.clear()
        self.drawer.append({'line': [np.asarray(sp), np.asarray(ep)]})
        for idx, xx in enumerate(x):
            if abs(avx - xx) > deta:
                pt = pts[idx]
                pt[0] = avx
                pt.insert(axis, points[idx][axis])
                self.drawer.append({'line': [points[idx], np.asarray(pt)]})
                # value = "侧弯{}" if std > deta else "垂直{}"
                # value = value.format(self.fmt_value(std))
                # return value, self.drawer

    def equal_distance(self, *points, deta=0.5, **kwargs):
        pa, pc, pb = points[:3]
        lac = np.linalg.norm(pc - pa)
        lbc = np.linalg.norm(pc - pb)
        # value = abs(lac - lbc)
        self.drawer.clear()
        self.drawer.append({'line': [pc, pa]})
        self.drawer.append({'line': [pc, pb]})
        return True if abs(lac - lbc) < deta else False, self.drawer

    # def curve_deflection(self, *points, power=3, seg=30, **kwargs):
    #     """
    #     用拉格朗日插值法计算曲线的挠度，如果点数太多，会出现龙格现象，后续应支持选择多种插值方法。
    #     :param args:
    #     :param kwargs:
    #     :return:
    #     """
    #     axis = self.axis_number(points)
    #     if axis is False:
    #         return "病灶不在同一平面。"
    #     if isinstance(axis, list):
    #         return "病灶处于同一水平线或者竖直线，挠度为0"
    #     pts = [np.asarray(self.xy_point(p, axis)) for p in points]
    #     cpts, value, mv_x, mv_c = self.curve_fit(pts, power=power, seg=seg)
    #     self.drawer.clear()
    #     for idx in range(len(cpts) - 1):
    #         sp = list(cpts[idx])
    #         ep = list(cpts[idx + 1])
    #         sp.insert(axis, points[0][axis])
    #         ep.insert(axis, points[0][axis])
    #         self.drawer.append(self.drawer.LINE, [np.asarray(sp), np.asarray(ep)])
    #     self.drawer.append(self.drawer.LINE, [points[0], points[-1]])
    #     mv_x = list(mv_x)
    #     mv_c = list(mv_c)
    #     mv_x.insert(axis, points[0][axis])
    #     mv_c.insert(axis, points[0][axis])
    #     self.drawer.append(self.drawer.LINE, [np.asarray(mv_x), np.asarray(mv_c)])
    #     self.drawer.append(self.drawer.TEXT, [0.5*(np.asarray(mv_x) + np.asarray(mv_c))], text=self.fmt_value(value))
    #     return value

    @staticmethod
    def curve_fit(points, power=2, seg=30):
        """
        :param points:二维点集
        :param power: 多项式阶数
        :param seg:返回结果的分段数目
        :return:
            pts: 按seg算出的曲线上的点。
            value：极大或者极小值。
            lc2w([mvx, 0])：极值对应的起始点连线上的垂点
            mvp：极值点
        """
        power = power if power < len(points) else len(points) - 1
        x0, y0 = points[-1][:2]
        x1, y1 = points[0][:2]

        dist = np.linalg.norm(points[-1][:2] - points[0][:2])

        cos_theta = (x1 - x0) / dist
        sin_theta = (y1 - y0) / dist

        w2lc_matrix = np.asarray([[cos_theta, sin_theta],
                                  [-sin_theta, cos_theta]])
        lc2w_matrix = np.asarray([[cos_theta, - sin_theta],
                                  [sin_theta, cos_theta]])
        w2lc = lambda p: w2lc_matrix.dot(np.asarray(p[:2]) - np.asarray([x0, y0]))
        lc2w = lambda p: lc2w_matrix.dot(np.asarray(p[:2])) + np.asarray([x0, y0])

        x = []
        y = []
        for p in points:
            lcp = w2lc(p)
            x.append(lcp[0])
            y.append(lcp[1])

        # curve = interpolate.lagrange(x, y)
        curve = np.poly1d(np.polyfit(x, y, power))
        # --求最大值
        dx = (x[-1] - x[0]) / seg
        pts = []
        value = 0
        mvp = None
        mvx = None
        for idx in range(seg + 1):
            xs = x[0] + idx * dx
            ys = curve(xs)
            wp = lc2w([xs, ys])
            if abs(ys) >= value:
                value = abs(ys)
                mvp = wp
                mvx = xs
            pts.append(wp)
        return pts, value, lc2w([mvx, 0]), mvp

        # def height_ratio(self, *points, **kwargs):
        #     """
        #     参照椎体高度指数计算
        #     :param args:
        #     :param kwargs:
        #     :return:
        #     """
        #     p1, p2, p3, p4 = points[:4]
        #     l12 = np.linalg.norm(p1 - p2)
        #     l34 = np.linalg.norm(p3 - p4)
        #     l13 = np.linalg.norm(p1 - p3)
        #     l24 = np.linalg.norm(p2 - p4)
        #     value = (l12 + l34) / (l13 + l24)
        #     self.drawer.clear()
        #     self.drawer.append(self.drawer.TEXT, [(p1 + p2 + p3 + p4) / 4.0], text=self.fmt_value(value))
        #     return value

        # def ratio_two_lines(self, *points, **kwargs):
        #     p1, p2, p3, p4 = points[:4]
        #     l12 = np.linalg.norm(p1 - p2)
        #     l34 = np.linalg.norm(p3 - p4)
        #     value = l12 / l34
        #     self.drawer.clear()
        #     self.drawer.append(self.drawer.TEXT, [sum(points[:4])/4.0], text=self.fmt_value(value))
        #     self.drawer.append(self.drawer.LINE, [p1, p2])
        #     self.drawer.append(self.drawer.LINE, [p3, p4])
        #     return value
        #
        # def ratio_two_lines_3pts(self, *points, **kwargs):
        #     p1, p2, p3 = points[:3]
        #     l12 = np.linalg.norm(p1 - p2)
        #     l23 = np.linalg.norm(p2 - p3)
        #     value = l12 / l23
        #     self.drawer.clear()
        #     self.drawer.append(self.drawer.TEXT, [sum(points[:3])/3.0], text=self.fmt_value(value))
        #     self.drawer.append(self.drawer.LINE, [p1, p2])
        #     self.drawer.append(self.drawer.LINE, [p2, p3])
        #     return value
        #
        # def ratio_three_lines_5pts(self, *points, **kwargs):
        #     """
        #     计算 [distance(p1, p2) + distance(p2 + p3)]/distance(p4, p5)
        #     """
        #     p1, p2, p3, p4, p5 = points[:5]
        #     l12 = np.linalg.norm(p2 - p1)
        #     l23 = np.linalg.norm(p3 - p2)
        #     l45 = np.linalg.norm(p5 - p4)
        #     value = (l12 + l23) / l45
        #     self.drawer.clear()
        #     self.drawer.append(self.drawer.TEXT, [sum(points[:5])/5.0], text=self.fmt_value(value))
        #     self.drawer.append(self.drawer.LINE, [p1, p2])
        #     self.drawer.append(self.drawer.LINE, [p2, p3])
        #     self.drawer.append(self.drawer.LINE, [p4, p5])
        #     return value


class Calculator(object):
    def __init__(self, points):
        self.points = points
        # self.operators = {}
        self.operators = collections.OrderedDict()
        self.calculate()

    def calculate(self):
        self.lumbar_scoliosis()
        self.isosceles_triangle()
        self.lumbar_transverse_diameter()
        self.interiliac_crest_line()

    def lumbar_scoliosis(self):
        """
        计算正位片棘突上缘LiSP连线是否在同一竖直线上。
        """
        args = [vt + "SP" for vt in AXIAL_VERTEBRAL]
        pts = [self.points[key][:2] for key in args]
        value, drawer_point = vertical_and_straight_line(pts)
        self.operators["lumbar_scoliosis"] = {'value': value, 'key_point': args, 'drawer_point': drawer_point}

    def isosceles_triangle(self, ):
        """
        计算正位片腰椎旋转
        """
        for vt in AXIAL_VERTEBRAL:
            a = self.points[vt + "MR"][:2]
            b = self.points[vt + "ML"][:2]
            c = self.points[vt + "SP"][:2]
            name = "isosceles_triangle-" + vt
            value, drawer_point = equal_distance(a, b, c, )
            # drawer_point = self.rois.equal_distance(*[a, b, c])
            self.operators[name] = {'value': value, 'key_point': [vt + "MR", vt + "ML", vt + "SP"],
                                    'drawer_point': drawer_point}

    def cobb(self):
        pass

    def lumbar_transverse_diameter(self):
        """
        计算腰椎管横径
        """
        for vt in AXIAL_VERTEBRAL:
            mr = self.points[vt + "MR"][:2]
            ml = self.points[vt + "ML"][:2]
            name = "lumbar_transverse_diameter-" + vt
            value, drawer_point = distance(*[mr, ml])
            self.operators[name] = {'value': value, 'key_point': [vt + "MR", vt + "ML"],
                                    'drawer_point': drawer_point}

    def interiliac_crest_line(self, ):
        '''
        客骨脊间连线
        :return: 用来展示画的三条线
        '''
        l5hr = self.points['L5HR'][:2]
        l5hl = self.points['L5HL'][:2]
        l5fr = self.points['L5FR'][:2]
        l5fl = self.points['L5FL'][:2]
        ir = self.points['IR'][:2]
        il = self.points['IL'][:2]
        drawer_point = [{'line': [l5hr, l5hl]}, {'line': [l5fr, l5fl]}, {'line': [ir, il]}]
        cut_range = [
            ((self.points['IR'][0]+self.points['IL'][0])/2,
             (self.points['L5HR'][1]+self.points['L5FL'][1])/2),
            ((self.points['IL'][0]-self.points['IR'][0])/2,
             (self.points['L5FL'][1]-self.points['L5HR'][1])/2
             )
        ]
        self.operators['interiliac_crest_line'] = {
            'value': -1,
            'key_point': ['L5HR', 'L5HL', 'L5FR', 'L5FL', 'IR', 'IL'],
            'drawer_point': drawer_point,
            'cut_range':cut_range
        }


class SagCalculator(object):
    def __init__(self, points):
        self.points = points
        self.operators = collections.OrderedDict()
        self.calculate()

    def calculate(self):
        self.lumbar_deflection()
        self.lumbar_curvature_angle()
        self.lumbar_height()
        self.lumbar_height_ratio()
        self.intervertebral_height()
        self.intervertebral_angle()
        self.sagittal_diameter()
        self.final_edition_angle()
        self.sacrum_tilt()
        self.lumbosacral_angle()
        self.slip_index()
        self.lumbosacral_slip_angle()

    def lumbar_deflection(self):
        """
        计算腰椎曲度
        """
        lcs = ["P", "A", "M"]
        for lc in lcs:
            names = ["T12" + "H" + lc, "T12" + "F" + lc]
            for lb in SAG_VERTEBRAL[1:-1]:
                names += [lb + "H" + lc, lb + "F" + lc]
            names += ["S1" + "H" + lc]
            pts = [self.points[key][:2] for key in names]
            drawer_point = [{'line': [self.points['T12H' + lc], self.points['L1H' + lc]]},
                            {'line': [self.points['L1H' + lc], self.points['L2H' + lc]]},
                            {'line': [self.points['L2H' + lc], self.points['L3H' + lc]]},
                            {'line': [self.points['L3H' + lc], self.points['L4H' + lc]]},
                            {'line': [self.points['L4H' + lc], self.points['L5H' + lc]]},
                            {'line': [self.points['L5H' + lc], self.points['S1H' + lc]]},
                            ]
            self.operators["lumbar_deflection-" + lc] = {'value': curve_deflection(pts, power=6),
                                                         'key_point': names, 'drawer_point': drawer_point}

    def lumbar_curvature_angle(self):
        """
        计算腰椎曲度角
        """
        v0_s = self.points["T12FP"][:2]
        v0_e = self.points["T12FA"][:2]
        v1_s = self.points["S1HP"][:2]
        v1_e = self.points["S1HA"][:2]
        value, drawer_point = degree_four_points(v0_s, v0_e, v1_s, v1_e)
        self.operators["lumbar_curvature_angle"] = {'value': value,
                                                    'key_point': ["T12FP", "T12FA", 'S1HP', 'S1HA'],
                                                    'drawer_point': drawer_point}

    def lumbar_height(self):
        lcs = ["P", "A", "M"]
        for lc in lcs:
            for l in SAG_VERTEBRAL[1:-1]:
                p0 = self.points[l + "H" + lc][:2]
                p1 = self.points[l + "F" + lc][:2]
                value, drawer_point = distance(p0, p1)
                self.operators["lumbar_height-" + l + "-" + lc] = {'value': value,
                                                                   'key_point': [l + "H" + lc, l + "F" + lc],
                                                                   'drawer_point': drawer_point}

    def lumbar_height_ratio(self):
        for l in SAG_VERTEBRAL[1:-1]:
            p1 = self.points[l + "HA"][:2]
            p2 = self.points[l + "HP"][:2]
            p3 = self.points[l + "FA"][:2]
            p4 = self.points[l + "FP"][:2]
            value, drawer_point = height_ratio(p1, p2, p3, p4)
            self.operators["lumbar_height_ratio-" + l] = {'value': value,
                                                          'key_point': [l + "HA", l + "HP", l + "FA", l + "FP"],
                                                          'drawer_point': drawer_point}

    def intervertebral_height(self):
        lcs = ["P", "M", "A"]
        for lc in lcs:
            for f in SAG_VERTEBRAL[:-1]:
                h = SAG_VERTEBRAL[SAG_VERTEBRAL.index(f) + 1]
                p0 = self.points[f + "F" + lc][:2]
                p1 = self.points[h + "H" + lc][:2]
                value, drawer_point = distance(p0, p1)
                self.operators["intervertebral_height-" + f + "_" + h + "-" + lc] = {'value': value,
                                                                                     'key_point': [f + "F" + lc,
                                                                                                   h + "H" + lc],
                                                                                     'drawer_point': drawer_point}

    def intervertebral_angle(self):
        for f in SAG_VERTEBRAL[:-1]:
            h = SAG_VERTEBRAL[SAG_VERTEBRAL.index(f) + 1]
            v0_s = self.points[f + "FP"][:2]
            v0_e = self.points[f + "FA"][:2]
            v1_s = self.points[h + "HP"][:2]
            v1_e = self.points[h + "HA"][:2]
            value, drawer_point = degree_four_points(v0_s, v0_e, v1_s, v1_e)
            self.operators["intervertebral_angle-" + f + "_" + h] = {
                'value': value,
                'key_point': [f + "FP", f + "FA", h + "HP", h + "HA"],
                'drawer_point': drawer_point
            }

    def sagittal_diameter(self):
        for l in SAG_VERTEBRAL[1:-1]:
            p = self.points[l + "SP"][:2]
            ls = self.points[l + "HP"][:2]
            le = self.points[l + "FP"][:2]
            value, drawer_point = distance_point_to_line(p, ls, le)
            self.operators["sagittal_diameter-" + l] = {'value': value,
                                                        'key_point': [l + "SP", l + "HP", l + "FP"],
                                                        'drawer_point': drawer_point}

    def final_edition_angle(self):
        ## 终板角，需要计算补角
        for l in SAG_VERTEBRAL[:-1]:
            for lc in ["H", "F"]:
                v0_s = self.points[l + lc + "C"][:2]
                v0_e = self.points[l + lc + "P"][:2]
                v1_e = v0_s
                v1_s = self.points[l + lc + "A"][:2]
                value, drawer_point = degree_four_points(v0_s, v0_e, v1_s, v1_e)
                self.operators["final_edition_angle-" + l + "-" + lc] = {'value': 180 - value,
                                                                         'key_point': [l + lc + "C", l + lc + "P",
                                                                                       l + lc + "A"],
                                                                         'drawer_point': drawer_point}

    def sacrum_tilt(self):
        """
        骶骨倾斜角
        """
        vs = self.points["S1HP"][:2]
        ve = self.points["S1HA"][:2]
        value, drawer_point = min_angle_between_vector_and_horizontal_line(vs, ve)

        self.operators["sacrum_tilt_horizontal"] = {'value': value,
                                                    'key_point': ['S1HP', 'S1HA'],
                                                    'drawer_point': drawer_point}
        vs = self.points["S1FP"][:2]
        ve = self.points["S1HP"][:2]
        value, drawer_point = min_angle_between_vector_and_vertical_line(vs, ve)
        self.operators["sacrum_tilt_vertical"] = {'value': value,
                                                  'key_point': ["S1FP", "S1HP"],
                                                  'drawer_point': drawer_point}

    def lumbosacral_angle(self):
        """
        腰骶夹角
        """
        v0_s = self.points["S1HA"][:2]
        v0_e = self.points["S1FA"][:2]
        v1_s = self.points["L5FA"][:2]
        v1_e = self.points["L5HA"][:2]
        value, drawer_point = degree_four_points(v0_s, v0_e, v1_s, v1_e)
        self.operators["lumbosacral_angle"] = {'value': value,
                                               'key_point': ["S1HA", "S1FA", "L5FA", "L5HA"],
                                               'drawer_point': drawer_point}

    def slip_index(self, seg=4):
        """
        滑脱分度
        """
        for f in SAG_VERTEBRAL[:-1]:
            h = SAG_VERTEBRAL[SAG_VERTEBRAL.index(f) + 1]
            p = self.points[f + "FP"][:2]
            ls = self.points[h + "HP"][:2]
            le = self.points[h + "HA"][:2]
            value = point_location(p, ls, le, seg=seg)
            drawer_point = [{'line': [ls, le]}, {'line': [p, ls]}]
            self.operators["slip_index-" + f + "_" + h] = {'value': value,
                                                           'key_point': [f + "FP", h + "HP", h + "HA"],
                                                           'drawer_point': drawer_point, }

    def lumbosacral_slip_angle(self):
        """
        腰骶滑脱角
        """
        v0_s = self.points["S1HP"][:2]
        v0_e = self.points["S1FP"][:2]
        v1_s = self.points["L5HA"][:2]
        v1_e = self.points["L5FA"][:2]
        value, drawer_point = degree_four_points(v0_s, v0_e, v1_s, v1_e)
        self.operators["lumbosacral_slip_angle"] = {'value': value,
                                                    'key_point': ["S1HP", "S1FP", "L5HA", "L5FA"],
                                                    'drawer_point': drawer_point}


def get_all_point_dict(csv_dir):
    # 逐行获取每张数据对应的节点名称和坐标组成的dict，存在一个list中
    csv_data = pd.read_csv(csv_dir)
    all_dict = []
    point_name = csv_data.columns.values.tolist()[2:]
    for index in range(len(csv_data)):
        point_anno = {}
        ## 输入csv文件中某一行，得到对应的dict
        for name in point_name:
            point_anno[name] = ([int(float(csv_data.loc[index, name].split(',')[-3].replace(' ', ''))),
                                 int(float(csv_data.loc[index, name].split(',')[-2].replace(' ', '')))])
        all_dict.append(point_anno)
    return all_dict


def get_result_zheng(csv_dir, save_dir):
    image_name = pd.read_csv(csv_dir)
    ## 根据传入的csv计算各项指标。正位片。并存在csv文件中
    columns = ['image_name', 'lumbar_scoliosis', 'isosceles_triangle-T12', 'isosceles_triangle-L1',
               'isosceles_triangle-L2', 'isosceles_triangle-L3', 'isosceles_triangle-L4',
               'isosceles_triangle-L5', 'lumbar_transverse_diameter-T12',
               'lumbar_transverse_diameter-L1', 'lumbar_transverse_diameter-L2',
               'lumbar_transverse_diameter-L3', 'lumbar_transverse_diameter-L4',
               'lumbar_transverse_diameter-L5']
    save_csv = pd.DataFrame(columns=columns)
    all_dict = get_all_point_dict(csv_dir)
    for index, dict in enumerate(all_dict):
        result_dict = Calculator(dict).operators
        result_dict['image_name'] = image_name.iloc[index, 0].split('/')[-1].replace('npz', '')
        save_csv = save_csv.append(pd.DataFrame(result_dict, index=[0]))
    save_csv = save_csv[columns]
    save_csv.to_csv(save_dir, index=False)


def get_result_cewei(csv_dir, save_dir):
    image_name = pd.read_csv(csv_dir)
    ## 根据传入的csv计算各项指标。正位片。并存在csv文件中
    columns = ['image_name', 'lumbar_deflection-P', 'lumbar_deflection-A', 'lumbar_deflection-M',
               'lumbar_curvature_angle',
               'lumbar_height-L1-P', 'lumbar_height-L2-P', 'lumbar_height-L3-P', 'lumbar_height-L4-P',
               'lumbar_height-L5-P', 'lumbar_height-L1-A', 'lumbar_height-L2-A', 'lumbar_height-L3-A',
               'lumbar_height-L4-A', 'lumbar_height-L5-A', 'lumbar_height-L1-M', 'lumbar_height-L2-M',
               'lumbar_height-L3-M', 'lumbar_height-L4-M', 'lumbar_height-L5-M', 'lumbar_height_ratio-L1',
               'lumbar_height_ratio-L2', 'lumbar_height_ratio-L3', 'lumbar_height_ratio-L4', 'lumbar_height_ratio-L5',
               'intervertebral_height-T12_L1-P', 'intervertebral_height-L1_L2-P', 'intervertebral_height-L2_L3-P',
               'intervertebral_height-L3_L4-P', 'intervertebral_height-L4_L5-P', 'intervertebral_height-L5_S1-P',
               'intervertebral_height-T12_L1-M', 'intervertebral_height-L1_L2-M', 'intervertebral_height-L2_L3-M',
               'intervertebral_height-L3_L4-M', 'intervertebral_height-L4_L5-M', 'intervertebral_height-L5_S1-M',
               'intervertebral_height-T12_L1-A', 'intervertebral_height-L1_L2-A', 'intervertebral_height-L2_L3-A',
               'intervertebral_height-L3_L4-A', 'intervertebral_height-L4_L5-A', 'intervertebral_height-L5_S1-A',
               'intervertebral_angle-T12_L1', 'intervertebral_angle-L1_L2', 'intervertebral_angle-L2_L3',
               'intervertebral_angle-L3_L4', 'intervertebral_angle-L4_L5', 'intervertebral_angle-L5_S1',
               'sagittal_diameter-L1', 'sagittal_diameter-L2', 'sagittal_diameter-L3', 'sagittal_diameter-L4',
               'sagittal_diameter-L5', 'final_edition_angle-T12-H', 'final_edition_angle-T12-F',
               'final_edition_angle-L1-H', 'final_edition_angle-L1-F', 'final_edition_angle-L2-H',
               'final_edition_angle-L2-F', 'final_edition_angle-L3-H', 'final_edition_angle-L3-F',
               'final_edition_angle-L4-H', 'final_edition_angle-L4-F', 'final_edition_angle-L5-H',
               'final_edition_angle-L5-F', 'sacrum_tilt_horizontal', 'sacrum_tilt_vertical', 'lumbosacral_angle',
               'slip_index-T12_L1', 'slip_index-L1_L2', 'slip_index-L2_L3', 'slip_index-L3_L4', 'slip_index-L4_L5',
               'slip_index-L5_S1', 'lumbosacral_slip_angle']
    save_csv = pd.DataFrame(columns=columns)
    all_dict = get_all_point_dict(csv_dir)
    for index, dict in enumerate(all_dict):
        result_dict = SagCalculator(dict).operators
        result_dict['image_name'] = image_name.iloc[index, 0].split('/')[-1].replace('npz', '')
        save_csv = save_csv.append(pd.DataFrame(result_dict, index=[0]))
    save_csv = save_csv[columns]
    save_csv.to_csv(save_dir, index=False)


if __name__ == '__main__':
    pts = [(841.278125, 861.04296875), (1039.709375, 862.6328125), (1271.16875, 859.0), (1501.109375, 861.8984375),
           (1748.2625, 867.740234375), (1929.640625, 898.703125)]
    rois = Rois()
    print()